/*
 * Copyright 2010 Chad Retz
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.jdocng.shared.model;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlType;

import com.google.common.annotations.GwtCompatible;
import com.google.gson.annotations.Expose;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.Type;

/**
 * Implementation of {@link ClassDoc}
 *
 * @author Chad Retz
 */
@XmlType(name = "class")
@GwtCompatible(serializable = true)
@SuppressWarnings("serial")
public class ClassDocImpl extends ProgramElementDocImpl implements ClassDoc {

    //exposed fields
    
    @Expose
    private ConstructorDocImpl[] constructors;
    
    @Expose
    private boolean definesSerializableFields;
    
    @Expose
    private FieldDocImpl[] enumConstants;
    
    @Expose
    private FieldDocImpl[] fields;
    
    @Expose
    private String[] importedClasses;
    
    @Expose
    private String[] importedPackages;
    
    @Expose
    private ClassDocImpl[] innerClasses;
    
    @Expose
    private String[] interfaces;
    
    @Expose
    private boolean externalizable;
    
    @Expose
    private boolean serializable;
    
    @Expose
    private MethodDocImpl[] methods;
    
    @Expose
    private String superClass;
    
    @Expose
    private TypeVariableImpl[] typeParameters;

    @Expose
    private ParamTagImpl[] typeParamTags;

    @Expose
    private TypeImpl type;
    
    //non-exposed fields
    
    private transient ClassDocImpl[] importedClassesLoaded;
    
    private transient PackageDocImpl[] importedPackagesLoaded;
    
    private transient ClassDocImpl[] interfacesLoaded;
    
    private transient Type[] interfaceTypesLoaded;
    
    private transient ClassDocImpl superClassLoaded;

    private transient Type superClassTypeLoaded;
    
    //ClassDoc interface impl
    
    @Override
    public ConstructorDocImpl[] constructors() {
        return constructors(true);
    }
    
    @Override
    public ConstructorDocImpl[] constructors(boolean filter) {
        if (!filter) {
            return constructors;
        }
        List<ConstructorDocImpl> ret = new ArrayList<ConstructorDocImpl>(constructors.length);
        for (ConstructorDocImpl constructor : constructors) {
            if (constructor.isIncluded()) {
                ret.add(constructor);
            }
        }
        return ret.toArray(new ConstructorDocImpl[0]);
    }
    
    @Override
    public boolean definesSerializableFields() {
        return definesSerializableFields;
    }
    
    @Override
    public FieldDocImpl[] enumConstants() {
        return enumConstants;
    }
    
    @Override
    public FieldDocImpl[] fields() {
        return fields(true);
    }
    
    @Override
    public FieldDocImpl[] fields(boolean filter) {
        if (!filter) {
            return fields;
        }
        List<FieldDocImpl> ret = new ArrayList<FieldDocImpl>(fields.length);
        for (FieldDocImpl field : fields) {
            if (field.isIncluded()) {
                ret.add(field);
            }
        }
        return ret.toArray(new FieldDocImpl[0]);
    }
    
    @Override
    public ClassDocImpl findClass(String className) {
        //in order based on interface javadoc
        //1. qualified name
        ClassDocImpl ret = getContext().getClassDoc(className);
        if (ret != null) {
            return ret;
        }
        //2. nested
        for (ClassDocImpl nested : innerClasses) {
            if (className.equals(nested.name())) {
                return nested;
            }
        }
        //3. package
        ret = containingPackage().findClass(className);
        if (ret != null) {
            return ret;
        }
        //4. class imports
        for (String imported : importedClasses) {
            if (className.equals(imported) ||
                    imported.endsWith("." + className)) {
                return getContext().getClassDoc(imported);
            }
        }
        //5. package imports
        for (PackageDocImpl imported : importedPackages()) {
            ret = imported.findClass(className);
            if (ret != null) {
                return ret;
            }
        }
        return null;
    }
    
    @Override
    public ClassDocImpl[] importedClasses() {
        if (importedClassesLoaded == null) {
            importedClassesLoaded = getContext().getClassDocs(importedClasses);
        }
        return importedClassesLoaded;
    }
    
    @Override
    public PackageDocImpl[] importedPackages() {
        if (importedPackagesLoaded == null) {
            importedPackagesLoaded = getContext().getPackageDocs(importedPackages);
        }
        return importedPackagesLoaded;
    }
    
    @Override
    public ClassDocImpl[] innerClasses() {
        return innerClasses(true);
    }
    
    @Override
    public ClassDocImpl[] innerClasses(boolean filter) {
        if (!filter) {
            return innerClasses;
        }
        List<ClassDocImpl> ret = new ArrayList<ClassDocImpl>(innerClasses.length);
        for (ClassDocImpl innerClass : innerClasses) {
            if (innerClass.isIncluded()) {
                ret.add(innerClass);
            }
        }
        return ret.toArray(new ClassDocImpl[0]);
    }
    
    @Override
    public ClassDocImpl[] interfaces() {
        if (interfacesLoaded == null) {
            interfacesLoaded = getContext().getClassDocs(interfaces);
        }
        return interfacesLoaded;
    }
    
    @Override
    public Type[] interfaceTypes() {
        if (interfaceTypesLoaded == null) {
            interfaceTypesLoaded = getContext().getTypes(interfaces);
        }
        return interfaceTypesLoaded;
    }
    
    @Override
    public boolean isAbstract() {
        return Modifier.isAbstract(modifierSpecifier());
    }
    
    @Override
    public boolean isExternalizable() {
        return externalizable;
    }
    
    @Override
    public boolean isSerializable() {
        return serializable;
    }
    
    @Override
    public MethodDocImpl[] methods() {
        return methods(true);
    }
    
    @Override
    public MethodDocImpl[] methods(boolean filter) {
        if (!filter) {
            return methods;
        }
        List<MethodDocImpl> ret = new ArrayList<MethodDocImpl>(methods.length);
        for (MethodDocImpl method : methods) {
            if (method.isIncluded()) {
                ret.add(method);
            }
        }
        return ret.toArray(new MethodDocImpl[0]);
    }
    
    @Override
    public FieldDocImpl[] serializableFields() {
        //should be stored?
        List<FieldDocImpl> ret = new ArrayList<FieldDocImpl>(fields.length);
        FieldDocImpl serializable = null;
        for (FieldDocImpl field : fields) {
            if (field.serialFieldTags().length > 0) {
                ret.add(field);
            } else if("serialPersistentFields".equals(field.name())) {
                serializable = field;
            }
        }
        if (ret.isEmpty() && serializable != null) {
            return new FieldDocImpl[] { serializable };
        }
        return ret.toArray(new FieldDocImpl[0]);
    }
    
    @Override
    public MethodDocImpl[] serializationMethods() {
        //I'm guessing here...hope it's right
        List<MethodDocImpl> ret = new ArrayList<MethodDocImpl>(methods.length);
        for (MethodDocImpl method : methods) {
            if (method.isSerializationMethod()) {
                ret.add(method);
            }
        }
        return ret.toArray(new MethodDocImpl[0]);
    }
    
    @Override
    public boolean subclassOf(ClassDoc cd) {
        if (isInterface()) {
            return "java.lang.Object".equals(cd.qualifiedName());
        }
        if (cd.qualifiedName().equals(superClass)) {
            return true;
        }
        return superclass().subclassOf(cd);
    }
    
    @Override
    public ClassDocImpl superclass() {
        if (superClassLoaded == null) {
            superClassLoaded = getContext().getClassDoc(superClass);
        }
        return superClassLoaded;
    }
    
    @Override
    public Type superclassType() {
        if (superClassTypeLoaded == null) {
            superClassTypeLoaded = getContext().getType(superClass);
        }
        return superClassTypeLoaded;
    }
    
    @Override
    public TypeVariableImpl[] typeParameters() {
        return typeParameters;
    }
    
    @Override
    public ParamTagImpl[] typeParamTags() {
        return typeParamTags;
    }
    
    //Type interface impl (simple delegation)
    
    @Override
    public AnnotationTypeDocImpl asAnnotationTypeDoc() {
        return type.asAnnotationTypeDoc();
    }
    
    @Override
    public ClassDocImpl asClassDoc() {
        return type.asClassDoc();
    }
    
    @Override
    public ParameterizedTypeImpl asParameterizedType() {
        return type.asParameterizedType();
    }
        
    @Override
    public TypeVariableImpl asTypeVariable() {
        return type.asTypeVariable();
    }
        
    @Override
    public WildcardTypeImpl asWildcardType() {
        return type.asWildcardType();
    }
    
    @Override
    public String dimension() {
        return type.dimension();
    }
    
    @Override
    public boolean isPrimitive() {
        return type.isPrimitive();
    }
    
    @Override
    public String qualifiedTypeName() {
        return type.qualifiedTypeName();
    }
    
    @Override
    public String simpleTypeName() {
        return type.simpleTypeName();
    }
    
    @Override
    public String toString() {
        return type.toString();
    }
    
    @Override
    public String typeName() {
        return type.typeName();
    }
    
    //other accessors
    
    public void setConstructors(ConstructorDocImpl[] constructors) {
        this.constructors = constructors;
    }
    
    public void setDefinesSerializableFields(boolean definesSerializableFields) {
        this.definesSerializableFields = definesSerializableFields;
    }
    
    public void setEnumConstants(FieldDocImpl[] enumConstants) {
        this.enumConstants = enumConstants;
    }
    
    public void setFields(FieldDocImpl[] fields) {
        this.fields = fields;
    }
    
    public String[] getImportedClasses() {
        return importedClasses;
    }
    
    public void setImportedClasses(String[] importedClasses) {
        this.importedClasses = importedClasses;
    }
    
    public String[] getImportedPackages() {
        return importedPackages;
    }
    
    public void setImportedPackages(String[] importedPackages) {
        this.importedPackages = importedPackages;
    }
    
    public void setInnerClasses(ClassDocImpl[] innerClasses) {
        this.innerClasses = innerClasses;
    }
    
    public String[] getInterfaces() {
        return interfaces;
    }
    
    public void setInterfaces(String[] interfaces) {
        this.interfaces = interfaces;
    }
    
    public void setExternalizable(boolean externalizable) {
        this.externalizable = externalizable;
    }
    
    public void setSerializable(boolean serializable) {
        this.serializable = serializable;
    }
    
    public void setMethods(MethodDocImpl[] methods) {
        this.methods = methods;
    }
    
    public String getSuperClass() {
        return superClass;
    }
    
    public void setSuperClass(String superClass) {
        this.superClass = superClass;
    }
    
    public void setTypeParameters(TypeVariableImpl[] typeParameters) {
        this.typeParameters = typeParameters;
    }
    
    public void setTypeParamTags(ParamTagImpl[] typeParamTags) {
        this.typeParamTags = typeParamTags;
    }
    
    public TypeImpl getType() {
        return type;
    }
    
    public void setType(TypeImpl type) {
        this.type = type;
    }
    
}
